from qqbot import *
from random import *
from threading import Thread
from functools import reduce
from os import path
from re import match
import re
import docker
import time
import datetime
import ctypes
import os
import threading
import time
import inspect
import ctypes

by_keyword = [
    ({"有"}, "有的有的，您把头发留下，我回邮箱给您", "没有，滚"),
    ({'我觉得', '我认为', "我以为"}, '呵呵，这只是你的认为'),
    ({"清华", "何昊天", "hht", "thupc", "thu"}, "清华败类"),
    ({"栽", "跟头", "呵呵", "爹"}, "走上社会，很快就会栽跟头", "你要是想打架，我就不奉陪了", "没给你打成伤残,看来我出的还是少了点",
     "现在的教育果然是失败", "一群愧对父母师长的学渣", "歇歇吧，五毛键盘侠们/微笑", "以己度人的事，劝你少干", "把聪明用在正道上", "骂人都这么'文雅'，还是你有文化。"),
    ({"葱"}, "葱太强了！", "葱强怎么了，我可是安庆拳王", "葱哥不要啊"),
    ({"zzs", "湛", "栈", "茄子"}, "听说zzs的尻很厉害", "我从未膜过如此强大之湛尻"),
    ({"打架", "打", "拳王", "傻", "小子", "残疾", "残", "爸爸", "cnm", "sb"}, "骂人都这么'文雅'，还是你有文化。", "你要是想打架，我就不奉陪了", "没给你打成伤残,看来我出的还是少了点", "现在的教育果然是失败",
     "一群愧对父母师长的学渣", "歇歇吧，五毛键盘侠们/微笑", "以己度人的事，劝你少干", "把聪明用在正道上"),
    # ({"有"}, "有的有的，您把头发留下，我回邮箱给您", "走上社会，很快就会栽跟头", "你要是想打架，我就不奉陪了", "没给你打成伤残,看来我出的还是少了点", "现在的教育果然是失败", "一群愧对父母师长的学渣", "歇歇吧，五毛键盘侠们/微笑", "以己度人的事，劝你少干",
    # "把聪明用在正道上", "信不信我雷普你", "你要是想打架，我就不奉陪了", "没给你打成伤残,看来我出的还是少了点", "现在的教育果然是失败", "一群愧对父母师长的学渣", "歇歇吧，五毛键盘侠们/微笑", "以己度人的事，劝你少干", "把聪明用在正道上", "信不信我雷>普你"),
    ({"胡老师",  "教龄", "向荣"}, "我胡老师20年教龄", "现在的教育果然是失败",
     "一群愧对父母师长的学渣", "歇歇吧，五毛键盘侠们/微笑", "以己度人的事，劝你少干", "把聪明用在正道上"),
    ({"病"}, "孩子，在独立自信之前，还是先明白天高地厚的道理", "水平低啊"),
    ({"颠倒黑白", "病", "水平"}, "水平低啊", "颠倒黑白", "你很是可笑", "你有病吧"),
    # ({"bot","机器人"}, "你才是bot！你全家都是bot！"),
    ({"rqy"}, "听说rqy很可爱", "rqyAK IOI 2019"),
    # ({"三去", "三", "法", "fa", "san", "wxh"}, "三去车仑大三去女子", "三仑车", "三去"),
    ({"好不好"}, "有的有的，您把头发留下，我回邮箱给您"),
    ({"放屁", "fuck"}, "噗——", "放你娘的屁！", "你很是可笑", "你有病吧"),
    ({"sb", "SB", "傻逼", "Sb"}, "放你娘的屁！", "你很是可笑", "你有病吧", "骂人都这么'文雅'，还是你有文化。"),
    ({"厉害", "您太强"}, "谢谢夸奖！"),
    ({"ysy", "咕咕", "咕"}, "闫咕咕"),
    ({"社会"}, "走上社会，很快就会栽跟头的"),
    ({"wxh", "王修涵", "天下第一"}, "wxh 是个什么东西"),
    ({"法法", "fa", "fafa", "法"}, "法法 女装 gay 发财"),
    ({"脑残"}, "脑残孩子"),
    ({"jhy", "金"}, "金赢赢哈哈"),
    ({"dzm", "邓"}, "大泽吗开吃特色菜", "邓找萌"),
    ({"rbq", "RBQ"}, "您全家都是RBQ"),
    # ({"你妈","nm","nmsl","龙","妈"},"f(n)没dp，你慢死了","武昌糯米白，农民少了"),
    ({"流氓"}, "是您/微笑"),
    ({"毒瘤"}, "当然是yql最毒瘤了！"),
    ({"学渣"}, "一群愧对父母师长的学渣", "歇歇吧，五毛键盘侠们/微笑", "以己度人的事，劝你少干", "把聪明用在正道上"),
    ({"qwq", "owo", "嘤", "ww", "喵"}, "小小年纪不学好卖什么萌/菜刀", "嘤嘤嘤", "喵~"),
    ({"人赢"}, "人赢！"),
    ({"好玩"}, "好玩好玩，非常好玩"),
    ({"ycr"}, "ycr报个真名出来，我让你们教练跟你说话，好好教教你做人！"),
    ({"题"}, "我向来反对问候出题人全家，但这一道除外", "我初中2个学生，15分钟A了，高中的2个加在一起用了近2h。"),
    ({"没钱"}, "那就女装援交"),
    ({"征婚"}, "有毒吧你"),
    ({"redbag", "红包"}, "哦"),
    ({"太弱了"}, "您太假了", "您太强了"),
    ({"太强了"}, "您太强了！")
]
by_random = [
    (2, "我就是安庆拳王胡向荣！", "感谢你的公正，不过本人不认为这是误会，所以拒绝任何形式的道歉，望理解。"),
    (10, "我胡老师20年教龄", "另外，本人从事计算机相关领域工作20余年，算法竞赛不到10年，水平确实不高。但作为一名奔5的高龄选手，即便不值得尊敬，至少也还可以平等相待吧。", "乱发我的语录很好玩？", "我初中2个学生，15分钟A了，高中的2个加在一起用了近2h。",
     "孩子，在独立自信之前，还是先明白天高地厚的道理", "水平低啊", "有的孩子品行太差", "上面的都是大牛，但不都是栋梁", "你不可能用辩论击败无知和缺德的人", "一路屡遭挫折，未敢止步不前。", "请看明日域中，竟是谁家天下 ？"),
    (3, "这话是有原因的", "真理也许会迟到，但也总是会缺席", "欲 罢 不 能"),
    (7, "你就这么和老师说话?", "难怪有那么多人选择了装孙子", "脑残孩子", "清华败类", "你有病吧，乱栽跟头", "有毒吧你"),
    (6, "这话是有原因的", "看来我出的还是少了点", "知乎上凡是支持我的言论都被删除了，网络暴力着实可怕！", "建议你先看看无问西东，再做认定。"),
    (6, "真是垃圾", "请不要让你的父母为你感到羞耻"),
    (7, "向来无理取闹，从不据理力争", "支持我的人很多，你可以到教练群里问问，不多教练不会像你们这班胡闹。"),
    (6, "不要啊，再这样人家要坏掉了qwq"),
]
by_regex = [
    # (".{3,}是.+","对对对是是是","呵呵，是")
    (".+[(不)(没有)].+吗.*", "不"),
    (".*你是.+吗.*", "你才是！你全家都是!"),
    # ("bot","你才是bot！你全家都是bot！"),
    (".*能.+吗.*", "不能，滚"),
    (".+是不是.+", "不是，滚")
]
by_fake = ["网络流氓@{} 冒充我，大家别在意/微笑",
           "@{} 你这个假人！居然敢冒充我拳王！", "没错，你才是真的，赶明儿你替我去挨打吧。"]
fake_names = ["hxr", "胡向荣"]
# active_groups = {}

COMMAND_HELP = "\
内置属性:\n\
rand_rate\t随机回复命中概率(0-100)\n\
interval\t两句话回复的间隔(ms)\n\
max_calc_time:一条calc指令执行的最长时间(ms)\n\
result_length_limit:calc返回值的最大长度(字符)\n\
repeat_rate:对于一句话，重复发一遍的概率\n\
active_groups:在哪些群里启用该机器人\n\
其他指令:\n\
exec [管理密码] [语句]\t执行一行Python代码(忽略返回值)\n\
get [管理密码] [表达式]\t计算表达式的值\n\
calc [表达式] \t在docker中运行一行Python代码\n\
vars\t打印内置属性的值"
# illegal_strs = {
#     "**", "pow", "open", "system", "exit", "Thread", "__",
#     "import", "bot", "SendTo", "contact", "bot", "global",
#     "getattr", "chr", "lambda", "法", "os", "轮", "config", "print", "help", "range", "sort"
# }
illegal_strs = {
    "法", "法轮", "真善", "19260817", "共产党"
}
last_send = {}
total = 0
config = {
    "rand_rate": 100,
    "interval": 8000,
    "result_length_limit": 100,
    "max_calc_time": 2000,
    "repeat_rate": 0,
    "rand_reply_rate": 50,
    "expression_length_limit": 200,
    "active_groups": {"LifeCraft", "qwq", "烟台二中", "山东",   "文化课", "IODS",  "粉丝群", "葱教总部"},
    "black_list_user": {
        "dzm"
    }
}


def _async_raise(tid, exctype):
    """raises the exception, performs cleanup if needed"""
    tid = ctypes.c_long(tid)
    if not inspect.isclass(exctype):
        exctype = type(exctype)
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
        tid, ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        # """if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"""
        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")


def stop_thread(thread):
    _async_raise(thread.ident, SystemExit)


def randitem(list):
    return list[randint(0, len(list)-1)]


def onPlug(qwq):
    global total
    print("胡向荣Bot启动中")
    for weight, *_ in by_random:
        total += weight


def get_reply_by_keyword(sender, content):
    global by_keyword
    for item in by_keyword:
        keys, *v = item
        for key_word in keys:
            if key_word in content:
                return randitem(v)
    return get_reply_by_random()


def get_reply_by_random():
    if not randint(0, 100) < config["rand_reply_rate"]:
        return
    global total, by_random
    for item in by_random:
        weight, *v = item
        if (randint(0, total) < weight):
            return randitem(v)


def get_reply_by_regex(content):
    for regex, *data in by_regex:
        if match(regex, content, flags=re.IGNORECASE) is not None:
            return randitem(data)


def auth(args):
    if not args.startswith("qwdhooqwiejewoa^&*ya*(xy("):
        return False
    else:
        return args[args.index(" ")+1:]


def execute_daemon_calc(bot, contact, member, expression, container):
    container.start()
    container.reload()
    while container.status == "running":
        container.reload()
    output = container.logs().decode("UTF-8")
    container.remove()
    if len(output) > config["result_length_limit"]:
        output = output[:config["result_length_limit"]]+"超出长度限制部分已截断"
    for item in illegal_strs:
        if item in output:
            output = "您的输出中有非法字符"
            break
    bot.SendTo(contact, output)
    # print("{} = {}".format(expression, output))


def keeper_thread(thread, bot, contact, container):
    time.sleep(config["max_calc_time"]/1000)
    container.reload()
    print("waiting time out,status={}".format(container.status))
    if container.status == "running":
        stop_thread(thread)
        try:
            container.kill()
            container.remove()
        except Exception:
            pass
        bot.SendTo(contact, "计算超时，已终止")
        print("killing")


def execute_calc(bot, contact, member, expression):
    if member is not None:
        if member.name in config["black_list_user"]:
            bot.SendTo(contact, "您不配使用本命令")
            return
    # for item in illegal_strs:
    #     if item in expression:
    #         bot.SendTo(contact, "您的表达式中有非法字符")
    #         return
    if len(expression) > config["expression_length_limit"]:
        bot.SendTo(contact, "您的表达式过长")
        return
    # try:
    #     bot.SendTo(contact,"{} = {}".format(expression,str(eval(expression))[:config["result_length_limit"]]))
    # except Exception as ex:
    #     bot.SendTo(contact,str(ex))
    # return
    client = docker.from_env()
    expr = expression
    if not os.path.exists("/tmp"):
        os.makedirs("/tmp")
    file_name = "{}-temp.py".format(int(time.time()))
    temp = open("/tmp/{}".format(file_name), "w")
    temp.write("{}".format(expr))
    temp.close()
    container = client.containers.create("python", "python /temp/{}".format(
        file_name), tty=True, detach=False,  volumes={"/tmp": {"bind": "/temp", "mode": "ro"}})
    thd = Thread(target=execute_daemon_calc, args=(
        bot, contact, member, expression, container))
    thd.setDaemon(True)
    thd.start()
    keeper = Thread(target=keeper_thread, args=(thd, bot, contact, container))
    keeper.setDaemon(True)
    keeper.start()

    # thd.setDaemon(True)


def handle_command(bot, contact, member, cmd, args):
    if cmd == "help":
        bot.SendTo(contact, COMMAND_HELP)
    elif cmd == "calc":
        execute_calc(bot, contact, member, args)
        # print("returned")
    elif cmd == "exec":
        args = auth(args)
        if not args:
            bot.SendTo(contact, "您没有权限执行此操作")
            return
        try:
            exec(args, globals())
            bot.SendTo(contact, "执行成功")
        except Exception as ex:
            bot.SendTo(contact, str(ex))
    elif cmd == "get":
        args = auth(args)
        if not args:
            bot.SendTo(contact, "您没有权限执行此操作")
            return
        try:
            bot.SendTo(contact, str(eval(args, globals())))
        except Exception as ex:
            bot.SendTo(contact, "{}".format(ex))
            raise ex
    elif cmd == "contents":
        bot.SendTo(contact, "关键字触发:\n{}\n随机触发:\n{}".format(
            by_keyword, by_random))
    elif cmd == "vars":
        bot.SendTo(contact, "\b".join(
            map(lambda item: "{} = {}\n".format(item, config[item]), config)))
    else:
        bot.SendTo(contact, "未知指令: {}".format(cmd))


def onQQMessage(bot, contact, member, content):
    global total,  last_send, config
    content = str(content)
    # 不处理自己的消息
    if member is not None:
        if getattr(member, 'uin', None) == bot.conf.qq:
            return

    # 判断一下是不是回复群
    if contact.ctype == "group":
        flag = False
        for item in config["active_groups"]:
            if item in contact.name:
                flag = True
                break
        if not flag:
            print("群 {} 不在回复列表里，消息忽略".format(contact.name))
            return
        for item in fake_names:
            if item in member.name:
                bot.SendTo(contact, randitem(by_fake).format(member.name))
                return
    curr = int(time.time()*1000)
    if contact.uin in last_send:
        if curr - last_send[contact.uin] < config["interval"]:
            print("对象 {} 距离再次发送消息还差间隔 {}".format(
                contact.name, curr-last_send[contact.uin]))
            return
    last_send[contact.uin] = curr
    # 处理指令
    if content.startswith("!"):
        content = content[1:].strip()
        if " " not in content:
            handle_command(bot, contact, member, content, "")
        else:
            command = content[:content.index(" ")]
            args = content[content.index(" ")+1:]
            handle_command(bot, contact, member, command, args)
        return
    if randint(0, 100) < config["repeat_rate"]:
        bot.SendTo(contact, content)
    if randint(0, 100) < config["rand_rate"]:
        x = get_reply_by_regex(content)
        if x is None:
            x = get_reply_by_keyword(contact, content)
        if x:
            if member is not None:
                x += " @{}".format(member.name)
            bot.SendTo(contact, x, resendOn1202=True)
